Lightning to use sync v2 formats#4722
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4722 +/- ##
==========================================
- Coverage 89.93% 89.85% -0.09%
==========================================
Files 444 444
Lines 21979 22110 +131
==========================================
+ Hits 19767 19866 +99
- Misses 2212 2244 +32 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Broaden the GitHub-sync repo+branch guard from "no ancestor may share" to "no project in the same tree may share" (root, sandboxes, siblings, cousins all share one (repo, branch) namespace), and move the enforcement from a check-then-insert pattern in Elixir to a Postgres unique index. Two concurrent transactions in the same project family can no longer both succeed at READ COMMITTED. - Add Lightning.Projects.root_id/1 walking parent_id to the top. - Migration adds project_repo_connections.root_project_id (NOT NULL after backfill) and a unique index on (root_project_id, repo, branch). - ProjectRepoConnection sets root_project_id on insert, declares unique_constraint(:branch, name: ...), and exposes tree_unique_violation?/1 so the boundary can translate the constraint violation back to :branch_used_in_project_tree. - VersionControl.create_github_connection drops its in-memory pre-flight check and relies on the unique index for race-safety. - Tests cover sibling/cousin collisions, the renamed error, and a direct INSERT race that's only resolvable by the index. Co-authored-by: Claude <noreply@anthropic.com>
Security Review ✅
|
|
@josephjclark , @midigofrank , sorry... dug deeper and this needs a few more tweaks. will be ready for review shortly. |
|
High level QA notes for this branch:
I've run a smoke test and it looks great - the format looks right and I can run a workflow yaml through the latest CLI version EDIT: QA looks good! Just one issue executing from the project, see below |
|
@taylordowns2000 I notice an issue here where when viewing workflow yaml, the credential is not included. It's fine when exporting the project. But this affects main too! So I'm going to raise it out as a bug, No action needed here. |
This is super interesting @josephjclark - the reason is likely that credentials are not included in templates and "view" is the precursor to "publish as template". How should we handle this? (a) Leave it as is |
|
@taylordowns2000 I'll take that convo over to #4731 |
|
@taylordowns2000 one change I'm going to recommend on both sides: let's add something like The CLI currently writes a This helps tools like the CLI and maybe later the provisioner understand and anticipate the structure they are about to parse. The practical upshot of this is that the CLI is erroring if you do Now, we should probably align that version with the portability spec v4. And we should probably considering semvering it (at least major and minor, maybe not patch) So I recommend:
This will also make it much easier to coordinate the next schema change we want to make. BTW I've made a separate issue for the "should portability spec be a JSON schema" thing. Still want to defer that for that though |
* Emit schema_version: '4.0' in v2 workflow and project YAML Adds a top-level `schema_version` key after `id` and `name` in both the TypeScript workflow serializer and the Elixir workflow/project serializer. The v2 JSON schema is extended to permit the key so round-trip parsing still passes its own validation. Parsers remain lenient — files without the key continue to parse unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * formatting on yaml output * add schema_version to v1 --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@midigofrank This is ready for you 👍 |
midigofrank
left a comment
There was a problem hiding this comment.
@josephjclark @taylordowns2000 I'm worried about the provisioner api. This PR replaces the yaml content being downloaded in the endpoint with v2 format, this will break older clis (not sure which versions).
I'll be happy to approve this if there is already a plan in place to handle legacy support
* restore v1 spec emitter * update test * force lowercase ids in v2 * lowercase in frontend
|
@midigofrank great catch on that. The provisioner API should not have broken, that should all sit on the v1 stuff, invisible to users. I've squashed a change which means:
So we should have working legacy automations, but a clean and tidy default v2 path going forward. I haven't looked closely at the GH sync stuff so I think I'll do that tomorrow |
|
@midigofrank has raised a really good flag The etophia deployment works by:
Obviously when we export the project in v2 style, this process will break. Are we aware of anyone else who uses the export function like this? options:
Option 1 feels like the most elegant here. All we've had to do in the CLI is convert formats on the fly - which we have good robust code for. |
|
Related, see this: OpenFn/kit#1421 |
| # Lets use the updated spec | ||
| specPath = Path.expand("test/fixtures/canonical_update_project.yaml") | ||
| specPath = | ||
| Path.expand("test/fixtures/portability/v1/canonical_update_project.yaml") |
There was a problem hiding this comment.
TODO:
- update the CLI to support v2 spec in v1 sync
- duplicate this test to use v2 spec
…ization Resolved conflicts: - CHANGELOG.md: merged unreleased entries from both branches - projects.ex: kept both root_id/1 and list_descendants/1 - projects_test.exs: kept describe blocks from both sides - export_utils.ex + webhook_reply fixture: kept deleted (V2 refactor) - yaml/util.ts: kept V2 pass-through refactor Note: channels and webhook_response_config export are not yet ported to the V2 serializer; the corresponding ported tests fail until a follow-up.
Brings two main-branch project-export features into the V2 portability format that the v2-sync refactor had left unported: - channels: emitted as a YAML array of objects (name, destination_url, enabled, destination_credential). The destination_credential reference uses V2's email|name credential-key form, consistent with a job's configuration: field. - webhook_response_config: nested under the webhook trigger step with the success_code / error_code the user has set; omitted when unset. Mirrored on the frontend (assets/js/yaml/v2.ts + schema) for webhook_response_config serialize/parse parity (channels are project-level, backend-only). The inherited tests asserted the v1/main shapes (a triggers: map and hyphenated credential refs); rewritten to the V2 steps-array shape. Also updated two pre-existing project-export tests that hadn't been adjusted when schema_version emission was added.
- Preserve webhook_response_config on import: the webhook branch of convertWorkflowSpecToState dropped the configured success/error status codes on every v2 import, unlike the cron and kafka branches. - Default a trigger's enabled to false on parse, matching the serializer, the Elixir emitter, and the Trigger schema default; parse previously defaulted to true, so the two directions of the module disagreed. - Round-trip JS conditions that collide with named condition literals: a js_expression body such as !state.errors is now preserved instead of being rewritten to on_job_success and dropping the expression. - Add `|` to the quote whitelist so credential keys (email|name) and `||` conditions stay unquoted, matching the Elixir emitter and avoiding byte-different YAML for the same workflow. Correct the stale module-doc comments and add round-trip tests covering each case.
The v2 project export preloaded the full credential chain (project_credential -> credential -> user) on every job and every channel, duplicating the project-level project_credentials that are already loaded. Build a project_credential_id -> "owner|name" lookup once from the project's credentials and resolve each job's configuration: and each channel's destination_credential: by id. workflow_struct_to_canonical now takes a :credential_keys option; serialize_workflow/1 has no project context, so it omits the option and falls back to the job's preloaded project_credential. Drop the redundant nested credential preloads from preload_project_for_export. Add a test covering a job whose configuration resolves from the project credentials by id.
…to v2-sync-finalization
Description
Closes #4718 .
Closes #4727 .
Switches Lightning to the v2 (portability spec) YAML format for the four user-facing surfaces below. The wire shape is now fully aligned with the OpenFn portability spec:
@openfn/kit/packages/lexicon/portability.d.ts.1. Canvas → "View workflow as code"
CodeViewPanelserializes the live workflow state to v2 YAML viaassets/js/yaml/format.ts(delegates toassets/js/yaml/v2.ts).1a. Export button
Same code path — the Download button writes the v2 YAML the panel renders.
1b. Publish as template button
TemplatePublishPanelwrites new templates in v2. Existing v1 templates remain readable on the Pick Template path (see 2a).2. Create new → "Import code as workflow"
YAMLImportPaneluses the format-aware façade: detects v1 vs v2 from pasted/uploaded YAML and dispatches to the matching parser. Empty-editor placeholder shows the v2 shape so new users land on the new format.2a. Pick template
TemplatePanelruns every template through the same façade. v1 templates load (lenient v1 parser), v2 templates parse strictly. A test asserts that the v1 and v2 versions of the same scenario produce structurally equivalent workflow state.3. Settings → "Export project" button
Projects.export_project/2callsLightning.Workflows.YamlFormat.serialize_project/2, which emits v2 YAML. The old v1 emitter (Lightning.ExportUtils) is removed.4. GitHub sync — block sandbox/ancestor branch collisions
A sandbox project can no longer link to the same
(repo, branch)as any of its ancestors:Projects.ancestor_ids/1walks theparent_idchain via a recursive CTE.ProjectRepoConnection.changeset/2validates against ancestor branches and surfaces an inline error in the Save form.VersionControl.create_github_connection/2re-checks the same condition transactionally so a concurrent insert can't race past the validation.Spec coverage
Fully implemented from the spec:
ProjectSpecid(required)ProjectSpecname?,description?ProjectSpecworkflows: WorkflowSpec[]ProjectSpeccredentials?: Credential[]ProjectSpeccollections?: string[]WorkflowSpecid?,name?idis the hyphenated nameWorkflowSpecsteps: Array<Job | Trigger>Stepid?,name?,next?StepEdgeboolean | string | ConditionalStepEdgeConditionalStepEdgecondition?(JS body),label?,disabled?Triggerenabled?Credentialname,owner(both required)Jobadaptors?: string[]Jobexpression?Jobconfiguration?: object | string<email>|<name>strings)Edge condition mapping
Lightning's internal
condition_typeenum ↔ speccondition(a JS expression body):condition:alwaysnext: <target>shortcut):on_job_success'!state.errors':on_job_failure'!!state.errors':js_expression<user JS body>(literal block when multi-line)Parser also accepts the spec's shortcuts:
next: { foo: true }→:alwaysnext: { foo: false }→:js_expressionbody"false"(Lightning has no:never)next: { foo: "<js>" }→:js_expressionbody verbatimLightning extensions (documented)
The portability spec marks
Triggerfields explicitly TODO (// TODO a trigger supports many more keys which the spec must support). Until the spec author fills these in, we keep two trigger-only extensions:type:discriminator (webhook/cron/kafka)openfn:block carrying cron expression, cron cursor reference, webhook reply mode, and Kafka configThese are commented as Lightning extensions in both
v2.exandv2.ts.What does NOT work yet (out of scope for this PR)
@openfn/cliintegration) is intentionally deferred — there is no Phase 5 import bridge in this branch.WorkflowSpec.globals,WorkflowSpec.start,WorkflowSpec.options,Step.previous,Job.configurationas object. Lightning has no internal equivalents today.test/integration/cli_deploy_test.exsis broken by the export cutover (it compares against a v1 fixture). Tagged:integrationand disabled by default; will be updated when the @openfn/cli suite is next exercised.Validation steps
id:,name:,steps:array, nojobs:/edges:, jobs useadaptors: [...], expression bodies underexpression: |.id:,workflows:is a sequence (- id: …),credentials:is a sequence of{name, owner},collections:is a sequence of names.!state.errors/!!state.errors.repo:main, create a sandbox of it, attempt to connect the sandbox torepo:main→ inline error, Save button disabled. Switch the sandbox torepo:dev→ Save enabled.AI Usage
Please disclose whether you've used AI anywhere in this PR (it's cool, we just
want to know!):
You can read more details in our
Responsible AI Policy
Pre-submission checklist
/reviewwith Claude Code)
(e.g.,
:owner,:admin,:editor,:viewer)